package Servants;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Scanner;
import java.util.concurrent.SynchronousQueue;

import org.omg.CORBA.IntHolder;

import core.Localmessage;

/**
 * This class is the implemetation object for your IDL interface.
 *
 * Let the Eclipse complete operations code by choosing 'Add unimplemented methods'.
 */

public class MyServiceServerImpl 
{
	
	/** General Declarations **/
	public static HashMap<String, Integer> Items = new HashMap<String, Integer>();/** To represent the server repository */
	HashMap ServerItems = new HashMap<String, Integer>(); /** the number of a spasific item in all the servers*/
	HashMap RespondeServerItems = new HashMap<String, String>(); /** the number of a spasific item in all the servers*/
	private HashMap<String,Integer> CustomerItems = new HashMap<String, Integer>(); /** To represent the customer items in the server */
	public enum REQUESTYPE {CHECK, HOLD,BUY,REPLY};
	private String name;
	/**
	 * Constructor for MyServiceServerImpl 
	 */
	public MyServiceServerImpl(String Name)
	{
		this.name = Name;
		fillHashMap();
		System.out.println(this.name + " is ready ...");
	}

	
	public String buy(String customerID, String itemID, int _numberOfItem) 
	{
		int numberOfItem = _numberOfItem;
		Integer existNumberOfItems = 0;
		int boughtItems = 0;
		int result = 0;
		this.readCustomer(customerID);
		Localmessage meServerM = new Localmessage();
		Localmessage meServerT = new Localmessage();
		Localmessage meServerV = new Localmessage();
		int RealnumberOfItem = numberOfItem;
		try
		{
			existNumberOfItems = Items.get(itemID);
			System.out.println(">>>>" + existNumberOfItems );
		}
		catch(Exception e)
		{
			//result = e.getMessage();
			System.out.println(result);
		}
		if(existNumberOfItems != null)
		{
			if(existNumberOfItems < numberOfItem)
			{
				numberOfItem = numberOfItem - existNumberOfItems;//In here we reserve the items the exist in this server.
				synchronized (Items.get(itemID)) 
				{
					Items.put(itemID, 0);
				}
				
				this.LcoalCheckItem(itemID);
				int [] number = new int[3];
				number[0] = (int) ServerItems.get("montreal");
				number[1] = (int) ServerItems.get("toronto");
				number[2] = (int) ServerItems.get("vancouver");
				if(numberOfItem <= (number[0] + number[1] + number[2]))
				{
					while(numberOfItem > 0)
					{
						if( number[0] > 0 && numberOfItem > 0)
						{
							System.out.println("Before Montreal :" + numberOfItem);
							if(number[0] >= numberOfItem)
							{
								meServerM.setNumberOfItems(numberOfItem);
								numberOfItem = 0;
								System.out.println(" After Montreal. " + numberOfItem);
							}
							else
							{
								numberOfItem = numberOfItem - number[0];
								meServerM.setNumberOfItems(number[0]);
								number[0] = 0;
							}
							meServerM.setMessageType("buy");
							meServerM.setItemID(itemID);
							UDPRequest(REQUESTYPE.BUY, meServerM, "montreal");
						}else if(number[1] > 0 && numberOfItem > 0)
						{
							System.out.println("Before Toronto :" + numberOfItem);
							if(number[1] >= numberOfItem)
							{
								meServerT.setNumberOfItems(numberOfItem);
								numberOfItem = 0;
							
							}
							else
							{
								numberOfItem = numberOfItem - number[1];
								meServerT.setNumberOfItems(number[1]);
								number[1] = 0;
							}
							meServerT.setMessageType("buy");
							meServerT.setItemID(itemID);
							UDPRequest(REQUESTYPE.BUY, meServerT, "toronto");
						
						}else if((number[2] > 0) && (numberOfItem > 0))
						{
							System.out.println("========= Before Vancouver :" + numberOfItem + number[2]);
							if(number[2] >= numberOfItem)
							{
								meServerV.setNumberOfItems(numberOfItem);
								numberOfItem = 0;
							}
							else
							{
								numberOfItem = numberOfItem - number[2];
								meServerV.setNumberOfItems(number[2]);
								number[2] = 0;
							}
							System.out.println("========= Before Vancouver :" + numberOfItem);
							meServerV.setMessageType("buy");
							meServerV.setItemID(itemID);
							UDPRequest(REQUESTYPE.BUY, meServerV, "vancouver");
						}
					
						if(CustomerItems.get(itemID) != null)
						{
							boughtItems = CustomerItems.get(itemID);
						}
						boughtItems = boughtItems + RealnumberOfItem;
						CustomerItems.put(itemID ,boughtItems );
						this.saveCustomer(customerID);
						result = _numberOfItem;
					}
				}
				else if(numberOfItem > (number[0] + number[1] + number[2]))
				{
					numberOfItem = numberOfItem + existNumberOfItems;//return the reserved items the exist in this server.
					synchronized (Items.get(itemID)) 
					{
						Items.put(itemID, existNumberOfItems);
					}
					result = 0;
				}
			}
			else
			{
				synchronized(Items.get(itemID))
					{
						if(CustomerItems.get(itemID) != null)
						{
							boughtItems = CustomerItems.get(itemID);
						}
						localBuy(itemID, numberOfItem);// substitue the items from the server repository
						boughtItems = boughtItems + numberOfItem;
						CustomerItems.put(itemID ,boughtItems );
						System.out.println(CustomerItems.get(itemID));
						this.saveCustomer(customerID);
						result = _numberOfItem;
					}
			}
		}

		
		else
		{
			result = -1;
		}
		
		
		return Integer.toString(result);

	}

	
	public String returnItem(String customerID, String itemID, int numberOfItem)
	{
		Integer existNumberOfItems = 0;
		int result = 0;
		Integer returnedItems = 0;
		this.readCustomer(customerID);
		try
		{
			returnedItems = CustomerItems.get(itemID);
		}
		catch(Exception e)
		{
			//result = e.getMessage();
			System.out.println(result);
		}
		if(returnedItems != null)
		{
			//here you need to see if the customer really has the number of items in his cart
			if(returnedItems < numberOfItem)
			{
				result = 1;
			}
			else
			{
				synchronized(Items.get(itemID))
				{
					
						existNumberOfItems = Items.get(itemID);
						existNumberOfItems = existNumberOfItems + numberOfItem;
						returnedItems = returnedItems - numberOfItem;
						CustomerItems.put(itemID ,returnedItems );
						Items.put(itemID, existNumberOfItems);
						this.saveCustomer(customerID);
						result = 1;
				}

			}
			System.out.println(Items.get(itemID));
		}
		else
		{
			result = -1;
		}
			return Integer.toString(result);
		
	}

	
	public String checkStock(String itemID) 
	{
		int temp = 0;
		this.LcoalCheckItem(itemID);
		temp = temp +  (int)ServerItems.get("montreal");
		temp = temp + (int)ServerItems.get("toronto");
		temp = temp + (int)ServerItems.get("vancouver");
		return Integer.toString(temp);
		
	}

	
	public String exchange(String customerID, String boughtItemID,
			int boughtNumber, String desiredItemID, int desiredNumber) 
	{
		int result = 0;
		this.readCustomer(customerID);
		Integer customerBoughtItems = 0;
		Integer localServerNumberOfItems = 0;
		Integer returnItemsTemp = 0;
		Integer buyItemsTemp = 0;
		int usedDesiredNumber = desiredNumber;
		Localmessage meServerM = new Localmessage();
		Localmessage meServerT = new Localmessage();
		Localmessage meServerV = new Localmessage();
		try
		{
			customerBoughtItems = CustomerItems.get(boughtItemID);
			localServerNumberOfItems = Items.get(desiredItemID);
		}
		catch(Exception e)
		{
			//result = e.getMessage();
			System.out.println(result);
		}
/** ============================================================================================================================ **/		
		if(customerBoughtItems != null && localServerNumberOfItems != null)
		{
			if(customerBoughtItems >= boughtNumber)
			{
/** ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^**/
				if(desiredNumber > localServerNumberOfItems)
				{
					/***************************************************************************************** **/
					usedDesiredNumber = usedDesiredNumber - localServerNumberOfItems;//In here we reserve the items the exist in this server.
					synchronized (Items.get(desiredItemID)) 
					{
						Items.put(desiredItemID, 0);
					}
					
					this.LcoalCheckItem(desiredItemID);
					int [] number = new int[3];
					number[0] = (int) ServerItems.get("montreal");
					number[1] = (int) ServerItems.get("toronto");
					number[2] = (int) ServerItems.get("vancouver");
					if(usedDesiredNumber <= (number[0] + number[1] + number[2]))
					{
						while(usedDesiredNumber > 0)
						{
							if( number[0] > 0 && usedDesiredNumber > 0)
							{
								System.out.println("Before Montreal :" + usedDesiredNumber);
								if(number[0] >= usedDesiredNumber)
								{
									meServerM.setNumberOfItems(usedDesiredNumber);
									usedDesiredNumber = 0;
									System.out.println(" After Montreal. " + usedDesiredNumber);
								}
								else
								{
									usedDesiredNumber = usedDesiredNumber - number[0];
									meServerM.setNumberOfItems(number[0]);
									number[0] = 0;
								}
								meServerM.setMessageType("buy");
								meServerM.setItemID(desiredItemID);
								UDPRequest(REQUESTYPE.BUY, meServerM, "montreal");
							}else if(number[1] > 0 && usedDesiredNumber > 0)
							{
								System.out.println("Before Toronto :" + usedDesiredNumber);
								if(number[1] >= usedDesiredNumber)
								{
									meServerT.setNumberOfItems(usedDesiredNumber);
									usedDesiredNumber = 0;
								
								}
								else
								{
									usedDesiredNumber = usedDesiredNumber - number[1];
									meServerT.setNumberOfItems(number[1]);
									number[1] = 0;
								}
								meServerT.setMessageType("buy");
								meServerT.setItemID(desiredItemID);
								UDPRequest(REQUESTYPE.BUY, meServerT, "toronto");
							
							}else if((number[2] > 0) && (usedDesiredNumber > 0))
							{
								System.out.println("========= Before Vancouver :" + usedDesiredNumber + number[2]);
								if(number[2] >= usedDesiredNumber)
								{
									meServerV.setNumberOfItems(usedDesiredNumber);
									usedDesiredNumber = 0;
								}
								else
								{
									usedDesiredNumber = usedDesiredNumber - number[2];
									meServerV.setNumberOfItems(number[2]);
									number[2] = 0;
								}
								System.out.println("========= Before Vancouver :" + usedDesiredNumber);
								meServerV.setMessageType("buy");
								meServerV.setItemID(desiredItemID);
								UDPRequest(REQUESTYPE.BUY, meServerV, "vancouver");
							}
						
							
							synchronized(Items.get(boughtItemID))
							{
								synchronized(Items.get(desiredItemID))
									{
										/** The implementation of the exchange*/
										returnItemsTemp = Items.get(boughtItemID);
										returnItemsTemp = returnItemsTemp + boughtNumber;
										customerBoughtItems = customerBoughtItems - boughtNumber;
										CustomerItems.put(boughtItemID ,customerBoughtItems );
										Items.put(boughtItemID, returnItemsTemp);
										//this.saveCustomer(customerID);
										if(CustomerItems.get(desiredItemID) != null)
										{
											buyItemsTemp = CustomerItems.get(desiredItemID);
										}
										//localServerNumberOfItems = localServerNumberOfItems - desiredNumber;
										buyItemsTemp =buyItemsTemp + desiredNumber;
										//Items.put(desiredItemID, localServerNumberOfItems);
										CustomerItems.put(desiredItemID ,buyItemsTemp );
										this.saveCustomer(customerID);
										result = 1;
								  }
							}
							
						}
					}
					else if(usedDesiredNumber > (number[0] + number[1] + number[2]))
					{
						
						
						usedDesiredNumber = usedDesiredNumber + localServerNumberOfItems;//return the reserved items the exist in this server.
						synchronized (Items.get(desiredItemID)) 
						{
							Items.put(desiredItemID, localServerNumberOfItems);
						}
						result = 0;
					}
					/********************************************************************************************/
				}
				else
				{// if the cstumer and the server have enough number of items to do the exchange
					synchronized(Items.get(boughtItemID))
					{
						synchronized(Items.get(desiredItemID))
							{
								/** The implementation of the exchange*/
								returnItemsTemp = Items.get(boughtItemID);
								returnItemsTemp = returnItemsTemp + boughtNumber;
								customerBoughtItems = customerBoughtItems - boughtNumber;
								CustomerItems.put(boughtItemID ,customerBoughtItems );
								Items.put(boughtItemID, returnItemsTemp);
								this.saveCustomer(customerID);
								if(CustomerItems.get(desiredItemID) != null)
								{
									buyItemsTemp = CustomerItems.get(desiredItemID);
								}
								localServerNumberOfItems = localServerNumberOfItems - desiredNumber;
								buyItemsTemp =buyItemsTemp + desiredNumber;
								Items.put(desiredItemID, localServerNumberOfItems);
								CustomerItems.put(desiredItemID ,buyItemsTemp );
								this.saveCustomer(customerID);
								result = 1;
						  }
					}
				}
/** ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^**/				
			}
			else
			{
				result = 2;
			}
		}
		else
		{
			result = -1;
		}
/** ============================================================================================================================ **/
		return Integer.toString(result);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * This method is used to check the oter server about a spacific items. 
	 * and save the item in hashMap 'ServerItems'
	 * @param ItemID
	 * @return
	 */
	public String LcoalCheckItem(String ItemID)
	{
		Localmessage serverOneMessage = new Localmessage();
		Localmessage serverTwoMessage = new Localmessage();
		
		Integer thisStoreItem = 0;
		String gg = "";
		try
		{
			thisStoreItem = Items.get(ItemID);
		}
		catch(Exception e)
		{
			gg = e.getMessage();

		}
		if(thisStoreItem != null)
		{
			gg = thisStoreItem.toString();
			System.out.println(Items.get(ItemID));
		}
		else
		{
			gg = "null";
		}
		switch(this.getName())
		{
		case "montreal":
			/***  ASK TORONTO  ***/
			serverOneMessage.setMessageType("check");
			serverOneMessage.setItemID(ItemID);
			serverOneMessage.setNumberOfItems(0);
			serverOneMessage.setState(true);
			serverOneMessage.setCnumberOfItems(0);
			ServerItems.put("toronto", UDPRequest(REQUESTYPE.CHECK, serverOneMessage, "toronto").getCnumberOfItems());
			/***  ASK VANCOUVER  ***/
			serverTwoMessage.setMessageType("check");
			serverTwoMessage.setItemID(ItemID);
			serverTwoMessage.setNumberOfItems(0);
			serverTwoMessage.setState(true);
			serverTwoMessage.setCnumberOfItems(0);
			ServerItems.put("vancouver", UDPRequest(REQUESTYPE.CHECK, serverTwoMessage, "vancouver").getCnumberOfItems());
			ServerItems.put("montreal", thisStoreItem);
			break;

		case "toronto":
			/***  ASK MONTREAL  ***/
			serverOneMessage.setMessageType("check");
			serverOneMessage.setItemID(ItemID);
			serverOneMessage.setNumberOfItems(0);
			serverOneMessage.setState(true);
			serverOneMessage.setCnumberOfItems(0);
			ServerItems.put("montreal", UDPRequest(REQUESTYPE.CHECK, serverOneMessage, "montreal").getCnumberOfItems());
			System.out.println(ServerItems.get("montreal"));
			/***  ASK VANCOUVER  ***/
			serverTwoMessage.setMessageType("check");
			serverTwoMessage.setItemID(ItemID);
			serverTwoMessage.setNumberOfItems(0);
			serverTwoMessage.setState(true);
			serverTwoMessage.setCnumberOfItems(0);
			ServerItems.put("vancouver", UDPRequest(REQUESTYPE.CHECK, serverTwoMessage, "vancouver").getCnumberOfItems());
			ServerItems.put("toronto", thisStoreItem);
			break;

		case "vancouver":
			/***  ASK TORONTO  ***/
			serverOneMessage.setMessageType("check");
			serverOneMessage.setItemID(ItemID);
			serverOneMessage.setNumberOfItems(0);
			serverOneMessage.setState(true);
			serverOneMessage.setCnumberOfItems(0);
			ServerItems.put("toronto", UDPRequest(REQUESTYPE.CHECK, serverOneMessage, "toronto").getCnumberOfItems());
			/***  ASK MONTREAL  ***/
			serverTwoMessage.setMessageType("check");
			serverTwoMessage.setItemID(ItemID);
			serverTwoMessage.setNumberOfItems(0);
			serverTwoMessage.setState(true);
			serverTwoMessage.setCnumberOfItems(0);
			ServerItems.put("montreal", UDPRequest(REQUESTYPE.CHECK, serverTwoMessage, "montreal").getCnumberOfItems());
			ServerItems.put("vancouver", thisStoreItem);
			break;
			
		}
			
		
		return "";
		
	}
	
	public Localmessage UDPRequest(REQUESTYPE RequestType, Localmessage message, String serverName)
	{
		String replatResult;
		Localmessage mes = message;
		DatagramSocket aSocket = null;
		try {
			aSocket = new DatagramSocket();    
			byte [] m = mes.constructMessage().getBytes();
			InetAddress aHost = InetAddress.getByName("localhost");
			int serverPort = 0;
			switch(serverName)
			{
			case "montreal":
				serverPort = 6789;
				break;
			case "toronto":
				serverPort = 6790;
				break;
			case "vancouver":
				serverPort = 6791;
				break;
			}
			DatagramPacket request =
			 	new DatagramPacket(m,  mes.constructMessage().length(), aHost, serverPort);
			aSocket.send(request);			                        
			byte[] buffer = new byte[1000];
			DatagramPacket reply = new DatagramPacket(buffer, buffer.length);	
			aSocket.receive(reply);
			System.out.println("Reply: " + new String(reply.getData()));
			mes.parseMessage(new String(reply.getData()));
		}catch (SocketException e){System.out.println("Socket: " + e.getMessage());
		}catch (IOException e){System.out.println("IO: " + e.getMessage());
		}finally {if(aSocket != null) aSocket.close();}

		return mes;
	}
	
	public int getItems(String ItemID)
	{
		return Items.get(ItemID);
	}
	
	/**
	 * This method is used to fill the HashMap with some items
	 */
	private void fillHashMap()
	{
		Items.put("1111",new Integer(23));
        Items.put("2222",new Integer(16));
        Items.put("3333",new Integer(18));
        Items.put("4444",new Integer(3));
//        Items.put("1005",new Integer(500));
//        Items.put("1006",new Integer(600));
//        Items.put("1007",new Integer(700));
//        Items.put("1008",new Integer(800));
	}
	
	/**
	 * This method is used to read customer file and save it in the customer HashMap
	 */
	public synchronized void readCustomer(String CustomerID)
	{
		Scanner scanner = null;
		try {
			scanner = new Scanner(new FileReader("DataFiles\\" + CustomerID + ".txt"));
			while (scanner.hasNextLine()) { 
				String[] columns = scanner.nextLine().split(" ");
				
				CustomerItems.put(columns[0], new Integer(columns[1])); 
			}
			System.out.println(CustomerItems);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} 
 
        scanner.close();
	}
	
	/**
	 * This method is used to save the customer hashmap items to the customer file
	 */
	public synchronized  void saveCustomer(String CustomerID)
	{
		OutputStream outputStream;
		try {
			outputStream = new FileOutputStream("DataFiles\\" + CustomerID + ".txt");
			Writer  writer = new OutputStreamWriter(outputStream);
		
			for (String key: CustomerItems.keySet())
			{ 
				try {
					writer.write(key);
				} catch (IOException e) {
					e.printStackTrace();
				} 
				writer.write(" "); 
				writer.write(String.valueOf(CustomerItems.get(key))); 
				writer.write("\n"); 
			} 
			writer.flush();
			writer.close();
		
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * This method is used to locally buy
	 */
	public String localBuy(String _itemID, int _numberOfItem)
	{
		int existNumberOfItems = Items.get(_itemID);
		if(existNumberOfItems < _numberOfItem)
		{
			return "error";
		}
		synchronized(Items.get(_itemID))
		{
				existNumberOfItems = existNumberOfItems - _numberOfItem;
				Items.put(_itemID, existNumberOfItems);
		}
		return "done";
	}
	public HashMap<String, Integer> getState()
	{
		return Items;
	}
	
	public void upData(HashMap<String, Integer> s)
	{
		Items = s;
	}
	
}
